package com.gitee.wsl.compose.chart.xy.axis

import androidx.compose.ui.unit.Dp
import com.gitee.wsl.common.ui.api.LabelAble
import com.gitee.wsl.compose.chart.xy.model.PointEntry

/**
 * Holds the offsets for major and minor ticks within an axis.
 */
 interface AxisState {
     val majorTickOffsets: List<Float>
     val minorTickOffsets: List<Float>
}

data class AxisStateImp(
    override val majorTickOffsets: List<Float>,
    override val minorTickOffsets: List<Float> = emptyList()
): AxisState

/**
 * The major and minor tick values for an axis.
 * @param T The type of the axis values.
 */
 interface TickValues<T> {
     val majorTickValues: List<T>
     val minorTickValues: List<T>
}

data class TickValuesImp<T>(
    override val majorTickValues: List<T>,
    override val minorTickValues: List<T> = emptyList()
) : TickValues<T>

/**
 * An interface for classes representing a plot axis.
 *
 * @param T The data type for the axis values.
 */
 interface AxisModel<T>: LabelAble<T> {


    /**
     * Computes major and minor tick values based on the minimum tick spacing and the overall
     * [axisLength].
     */
     fun computeTickValues(axisLength: Dp): TickValues<T>

    /**
     * Computes the linear offset of the provided point along this axis relative to its min value.
     * For a linear axis this is offset = (point-min)/(max-min). Values less than 0 or greater
     * than 1 mean the point is before or beyond the range of the axis, respectively.
     * Nonlinear, e.g. log, axes can be implemented with appropriate transformations in this
     * function.
     */
    fun computeOffset(point: T): Float

    fun compute(axisLength: Dp):Pair<AxisState, TickValues<T>>{
        val tickValues = computeTickValues(axisLength)

        val axisState = object : AxisState {
            override val majorTickOffsets: List<Float> = tickValues.majorTickValues.map { computeOffset(it) }
            override val minorTickOffsets: List<Float> = tickValues.minorTickValues.map { computeOffset(it) }
        }

        return axisState to tickValues
    }

    /**
     * Asks the AxisState to compute new ranges and tick values after zooming, if the axis supports
     * zooming.
     *
     * @param zoomFactor Amount to zoom about the [pivot]. Must be greater than 0,
     * where values less than 1 zoom out and values greater than 1 zoom in.
     * @param pivot The point around which to zoom. Must be between 0 and 1, and represents the
     * distance of the pivot point from the minimum value as a fraction of the overall range
     * of the AxisState. That is, to zoom about a particular point, pivot should be the value
     * returned by [computeOffset].
     */
    fun zoom(zoomFactor: Float, pivot: Float) {}

    /**
     * Asks the AxisState to compute new ranges and tick values after panning, if the axis
     * supports panning.
     *
     * @param amount to pan as a fraction of the overall axis size. e.g. a value of 0.1 will pan
     * the axis by 10% of its current range, increasing the values. Negative values will pan by
     * decreasing the minimum and maximum axis range.
     */
    fun pan(amount: Float): Boolean {
        return false
    }


    fun transform(
        length: Int,
        centroid: Float,
        pan: Float,
        zoom: Float
    ) {
        val pivot = (length.toFloat() - centroid) / length.toFloat()
        zoom(zoom, pivot.coerceIn(0f, 1f))
        pan(pan / length.toFloat())
    }

    companion object

}

 interface LinearAxisModel<T> : AxisModel<T> where T : Comparable<T>, T : Number {
     val range: ClosedRange<T>

     /**
      * Specifies the minimum spacing for major ticks, in Dp units. Must be greater than 0.
      */
     val minimumMajorTickSpacing: Dp
}



internal fun <X, Y> List<PointEntry<X, Y>>.toXList(): List<X> = object : AbstractList<X>() {
    override val size: Int
        get() = this@toXList.size

    override fun get(index: Int): X {
        return this@toXList[index].x
    }
}

internal fun <X, Y> List<PointEntry<X, Y>>.toYList(): List<Y> = object : AbstractList<Y>() {
    override val size: Int
        get() = this@toYList.size

    override fun get(index: Int): Y {
        return this@toYList[index].y
    }
}


/**
 * Returns the sign of the given value [x]:
 *   - `-1.0` if the value is negative,
 *   - zero if the value is zero,
 *   - `1.0` if the value is positive
 *
 * Special case:
 *   - `sign(NaN)` is `NaN`
 */
internal fun sign(x: Long): Long {
    return if (x < 0L) {
        -1L
    } else if (x > 0L) {
        1L
    } else {
        0L
    }
}

/**
 * Returns the sign of the given value [x]:
 *   - `-1.0` if the value is negative,
 *   - zero if the value is zero,
 *   - `1.0` if the value is positive
 *
 * Special case:
 *   - `sign(NaN)` is `NaN`
 */
internal fun sign(x: Int): Int {
    return if (x < 0) {
        -1
    } else if (x > 0) {
        1
    } else {
        0
    }
}
